﻿using System;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.ComponentModel;
using System.Collections;
using System.Text.RegularExpressions;

namespace ClipperHouse.jQuery.Base
{
	[ParseChildren(true)]
	public class JQuery : CompositeControl, IJQuery
	{
		public JQuery()
		{
			this.IsCallbackEnabled = true;	// Most effects are enabled for callback, so default to true. For those that aren't, set to false in their constructors.
			this.CallbackScript = new InnerProperty();
			this.Chain = new List<JQuery>();
			this.Callback = new List<JQuery>();
			this.ChildExpressions = new List<JQuery>();
			this.Inline = false;
			this.Indent = 1;
			this.IsEnd = true;
			_SelectorPrefix = SelectorPrefix.DollarSign;
			this.IsSelectorPrefixSet = false;
		}

		public JQuery(string target)
			: this()
		{
			this.Target = target;
		}

		public JQuery(Control control)
			: this("#" + control.ClientID)
		{
		}

		/// <summary>
		/// When ready, the Effect simply adds itself to the ScriptManager. The ScriptManager in turn will output the Effect.Javascript property.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnLoad(EventArgs e)
		{
			if (this.Enabled)
			{
				bool isChild = (this.Parent is IJQuery);

				if (isChild)
				{
					JQuery parentAsEffect = (JQuery)this.Parent;
					parentAsEffect.Chain.Add(this);
				}
				else
				{
					if (!this.Inline)
					{
						ScriptManager scriptManager = this.ScriptManagerInstance;

						if (scriptManager != null)
						{
							scriptManager.JQueryControls.Add(this);
						}
						else
						{
							throw new Exception("Cannot locate a jQuery.ScriptManager. Please place a ClipperHouse.jQuery ScriptManager control in your Page or Master page.");
						}
					}
				}
			}
			base.OnLoad(e);
		}

		public override void RenderControl(HtmlTextWriter writer)
		{
			if (this.Inline)
			{
				if (this.Enabled)
				{
					string output = this.Javascript.ToDocumentReady(this.SelectorPrefix).ToScriptBlock();
					writer.Write(output);
				}
			}
			else
			{
				base.RenderControl(writer);
			}
		}

		/// <summary>
		/// Ensure no wrapper tags, like span
		/// </summary>
		protected override string TagName
		{
			get
			{
				return String.Empty;
			}
		}

		/// <summary>
		/// The jQuery library method name, eg, fadeIn
		/// </summary>
		public virtual string jQueryMethodName { get; set; }

		public override string ToString()
		{
			return base.ToString() + " (" + this.Target + ") ";
		}

		/// <summary>
		/// The Javascript that to be output by the ScriptManager
		/// </summary>
		public string Javascript
		{
			get
			{
				this.Indent = this.Indent + 1;
				NameValueCollection triggerParams = new NameValueCollection();
				triggerParams.Add("function", this.TargetJavascript.TrimWhitespace().ToFunctionExpression(true, this.Indent));

				string javascript = new JQueryExpression()
				{
					Selector = this.ParseServerContolSelectors(this.TriggerToUse),
					SelectorPrefix = this.SelectorPrefix,
					Method = this.TriggerEvent.ToString().ToLower(),
					Params = triggerParams,
					Indent = this.Indent - 1,
					IsEnd = true
				}
				.Javascript;

				return javascript;
			}
		}

		#region Rendering options
		/// <summary>
		/// Set to true to output the Javascript at the location of the tag
		/// </summary>
		public bool Inline
		{
			get;
			set;
		}

		public int Indent
		{
			get;
			set;
		}

		protected bool IsEnd
		{
			get;
			set;
		}
		#endregion

		protected bool IsEvent
		{
			get;
			set;
		}

		#region Triggers

		/// <summary>
		/// jQuery-style selector of the control(s) which trigger the effect. For multiple selectors, separate with commas.
		/// See http://docs.jquery.com/Selectors
		/// </summary>
		public string Trigger
		{
			get;
			set;
		}

		public string TriggerToUse
		{
			get
			{
				if (String.IsNullOrEmpty(this.Trigger) && !String.IsNullOrEmpty(this.Target))
				{
					return this.Target;
				}
				return this.Trigger;
			}
		}

		/// <summary>
		/// Event which triggers the effect, eg, Click or MouseOver. Default is Click.
		/// </summary>
		public TriggerEvent TriggerEvent
		{
			get;
			set;
		}

		#endregion

		#region Targets

		/// <summary>
		/// jQuery-style selector of the control(s) which the effect will act on. For multiple selectors, separate with commas.
		/// See http://docs.jquery.com/Selectors
		/// </summary>
		public string Target
		{
			get;
			set;
		}

		public string TargetToUse
		{
			get
			{
				if (this.Parent is JQuery)
				{
					JQuery jqParent = this.Parent as JQuery;
					if (!String.IsNullOrEmpty(jqParent.Target) && this.Target == jqParent.Target)
					{
						return "this";
					}
				}
				else if (!String.IsNullOrEmpty(this.Target) && this.Target == this.TriggerToUse)
				{
					//return "this";
				}

				return this.Target;
			}
		}

		protected string TargetJavascript
		{
			get
			{
				string targetJavascript = String.Empty;

				List<JQuery> childrenForCallback = this.Callback;	//.Where(c => c.IsEvent == false).ToList();
				List<JQuery> childrenToChain = this.Chain;	//.Where(c => c.IsEvent == false).ToList();
				List<JQuery> childrenExpressions = this.ChildExpressions;

				if (this.ChildExpressions.Count > 0)
				{
					foreach (JQuery child in childrenExpressions)
					{
						targetJavascript += child.TargetJavascript + Environment.NewLine;
					}
				}
				else
				{
					string childCallbackExpr = String.Empty;
					if (this.IsCallbackEnabled)
					{
						foreach (JQuery child in childrenForCallback)
						{
							if (String.IsNullOrEmpty(child.Target))		// Ensure callbacks have selectors
							{
								child.Target = "this";
							}

							child.SelectorPrefix = this.SelectorPrefix;
							child.Indent = this.Indent + 1;
							child.IsEnd = true;
							childCallbackExpr += child.TargetJavascript + Environment.NewLine;
						}

						if (!String.IsNullOrEmpty(childCallbackExpr))
						{
							childCallbackExpr += Utility.Indent(this.Indent + 1);
							this.CallbackScript.Text = childCallbackExpr + this.CallbackScript.Text.TrimWhitespace();
						}

						OnCallbackSet();

						if (!String.IsNullOrEmpty(this.CallbackExpression))
						{
							this.Params.Add("callback", this.CallbackExpression);
							if (this.AllowedParams.Count > 0)
							{
								this.AllowedParams.Add("callback");
							}
						}
					}

					this.IsEnd = this.IsEnd && (childrenToChain.Count == 0);

					targetJavascript = new JQueryExpression()
					{
						Selector = this.ParseServerContolSelectors(this.TargetToUse),
						SelectorPrefix = this.SelectorPrefix,
						Method = this.jQueryMethodName,
						Params = this.Params,
						IsChain = String.IsNullOrEmpty(this.Target),
						Indent = this.Indent,
						IsEnd = this.IsEnd
					}
					.Javascript;

					foreach (JQuery child in childrenToChain)
					{
						// targets and triggers are irrelevant for chained effects
						child.Target = String.Empty;
						child.Trigger = String.Empty;
						child.SelectorPrefix = this.SelectorPrefix;
						child.IsEnd = (child == childrenToChain.Last()) || !this.IsCallbackEnabled;
						child.Indent = this.Indent;

						if (!this.IsCallbackEnabled)
						{
							targetJavascript += Environment.NewLine;
						}
						else
						{
							child.Indent += 1;
						}
						targetJavascript += child.TargetJavascript;
					}
				}
				return targetJavascript;
			}
		}

		protected string TargetJavascriptOld
		{
			get
			{
				List<JQuery> allChildren = this.Chain.Where(c => c.IsEvent == false).ToList();

				List<JQuery> childrenWithTargets = (from c in allChildren
													where !String.IsNullOrEmpty(c.Target)
													select c).ToList();

				List<JQuery> childrenWithoutTargets = (from c in allChildren
													   select c).Except(childrenWithTargets).ToList();

				List<JQuery> childrenForCallback = childrenWithTargets;
				List<JQuery> childrenToAppend = childrenWithoutTargets;

				bool childrenHaveTargets = (childrenWithTargets.Count > 0);

				if (this.IsCallbackEnabled)
				{
					if (childrenHaveTargets)
					{
						childrenForCallback = allChildren;

						foreach (JQuery child in childrenForCallback)
						{
							if (String.IsNullOrEmpty(child.Target))
							{
								child.Target = "this";
							}
						}
						childrenToAppend.Clear();
					}
				}

				if (this.IsCallbackEnabled)
				{
					string childCallbackExpr = String.Empty;
					foreach (JQuery child in childrenForCallback)
					{
						child.SelectorPrefix = this.SelectorPrefix;
						child.Indent = this.Indent + 1;
						child.IsEnd = true;
						childCallbackExpr += child.TargetJavascript + Environment.NewLine;
					}

					if (!String.IsNullOrEmpty(childCallbackExpr))
					{
						childCallbackExpr += Utility.Indent(this.Indent + 1);
						this.CallbackScript.Text = childCallbackExpr + this.CallbackScript.Text.TrimWhitespace();
					}

					OnCallbackSet();
				}

				if (this.IsCallbackEnabled && !String.IsNullOrEmpty(this.CallbackExpression))
				{
					this.Params.Add("callback", this.CallbackExpression);
					if (this.AllowedParams.Count > 0)
					{
						this.AllowedParams.Add("callback");
					}
				}

				this.IsEnd = this.IsEnd && (childrenToAppend.Count == 0);

				string targetJavascript = new JQueryExpression()
				{
					Selector = this.ParseServerContolSelectors(this.TargetToUse),
					SelectorPrefix = this.SelectorPrefix,
					Method = this.jQueryMethodName,
					Params = this.Params,
					IsChain = String.IsNullOrEmpty(this.Target) && !childrenHaveTargets,
					Indent = this.Indent,
					IsEnd = this.IsEnd
				}
				.Javascript;

				// (childrenToAppend.Count == 0) || !this.IsCallbackEnabled

				foreach (JQuery child in childrenToAppend)
				{
					child.SelectorPrefix = this.SelectorPrefix;
					child.IsEnd = (child == childrenToAppend.Last()) || !this.IsCallbackEnabled;
					child.Indent = this.Indent;

					if (!this.IsCallbackEnabled)
					{
						targetJavascript += Environment.NewLine;
					}
					else
					{
						child.Indent += 1;
					}
					targetJavascript += child.TargetJavascript;
				}

				List<JQuery> eventChildren = this.Chain.Where(c => c.IsEvent == true).ToList();
				foreach (JQuery child in eventChildren)
				{
					string eventJavascript = new JQueryExpression()
					{
						Selector = (String.IsNullOrEmpty(child.Target) ? "this" : child.Target),
						SelectorPrefix = this.SelectorPrefix,
						Method = child.jQueryMethodName,
						Params = child.Params,
						IsChain = false,
						Indent = this.Indent,
						IsEnd = true
					}
					.Javascript;
					targetJavascript += Environment.NewLine + eventJavascript;
				}

				return targetJavascript;
			}
		}

		#endregion

		#region Selectors

		/// <summary>
		/// Prefix to use for the jQuery effect: either "$" or "jQuery"
		/// </summary>
		public SelectorPrefix SelectorPrefix
		{
			get
			{
				return _SelectorPrefix;
			}
			set
			{
				_SelectorPrefix = value;
				IsSelectorPrefixSet = true;
			}
		}
		private SelectorPrefix _SelectorPrefix;
		protected bool IsSelectorPrefixSet
		{
			get;
			set;
		}

		protected List<String> reservedSelectors = new List<string> { "this", "window", "document", "body", "document.body" };

		public string ParseServerContolSelectors(string s)
		{
			string selectorsResult = s;
			string selectorPrefix = @"%";

			if (!String.IsNullOrEmpty(selectorsResult))
			{
				// Look for ASP.net control IDs

				List<string> selectors = selectorsResult.Split(",".ToCharArray()).ToList();
				List<string> aspNetSelectors = (from selector in selectors
												where selector.Trim().StartsWith(selectorPrefix)
												select selector.Trim()).ToList();

				foreach (string aspNetSelector in aspNetSelectors)
				{
					Control foundControl = null;
					string controlId = aspNetSelector.Substring(selectorPrefix.Length, aspNetSelector.Trim().Length - selectorPrefix.Length);

					if (foundControl == null && this.Parent != null)
					{
						foundControl = Utility.FindControlRecursive(this.Parent, controlId);
					}

					if (foundControl == null && this.Page != null)
					{
						foundControl = Utility.FindControlRecursive(this.Page, controlId);
					}

					if (foundControl == null && this.Page.Master != null)
					{
						foundControl = Utility.FindControlRecursive(this.Page.Master, controlId);
					}

					if (foundControl != null)
					{
						selectorsResult = selectorsResult.Replace(aspNetSelector, "#" + foundControl.ClientID);
					}
				}
			}
			return selectorsResult;
		}
		#endregion

		#region ScriptManager

		private ScriptManager _ScriptManager = null;
		protected ScriptManager ScriptManagerInstance
		{
			get
			{
				if (_ScriptManager == null)
				{
					System.Web.UI.Control findControl = null;

					if (_ScriptManager == null && this.Page != null)
					{
						findControl = Utility.FindControlRecursive(this.Page, typeof(ScriptManager));
						if (findControl != null)
						{
							_ScriptManager = (ScriptManager)findControl;
							return _ScriptManager;
						}
					}

					if (_ScriptManager == null && this.Page != null && this.Page.Master != null)
					{
						findControl = Utility.FindControlRecursive(this.Page.Master, typeof(ScriptManager));
						if (findControl != null)
						{
							_ScriptManager = (ScriptManager)findControl;
							return _ScriptManager;
						}
					}
				}
				return _ScriptManager;
			}
		}

		#endregion
		
		#region Params
		protected string ParamsExpression
		{
			get
			{
				string paramsExpression = String.Empty;

				List<string> paramsKeys;

				if (this.IsCallbackEnabled && !String.IsNullOrEmpty(this.CallbackExpression))
				{
					this.Params.Add("callback", this.CallbackExpression);
					if (this.AllowedParams.Count > 0)
					{
						this.AllowedParams.Add("callback");
					}
				}

				if (this.AllowedParams.Count > 0)
				{
					paramsKeys = this.AllowedParams;
				}
				else
				{
					paramsKeys = this.Params.AllKeys.ToList();
				}

				string paramDelimiter = ", ";

				foreach (string paramKey in paramsKeys)
				{
					string paramExpression = String.Empty;

					if (this.Params[paramKey] != null && !String.IsNullOrEmpty(this.Params[paramKey].ToString()))
					{
						string paramAsString = this.Params[paramKey].ToString().Trim();

						paramExpression = paramAsString.ToJSParam();

						paramsExpression += paramExpression;
						paramsExpression += paramDelimiter;
					}
				}

				if (paramsExpression.EndsWith(paramDelimiter))
				{
					paramsExpression = paramsExpression.Substring(0, paramsExpression.Length - paramDelimiter.Length);
				}

				return paramsExpression;
			}
		}

		protected NameValueCollection Params
		{
			get
			{
				return _Params;
			}
			set
			{
				_Params = value;
			}
		}
		private NameValueCollection _Params = new NameValueCollection();

		/// <summary>
		/// For certain effects, the use of one param precludes the use of another. Adding elements to
		/// this list will exclude all otehr specified params. If this list has no elements, it is ignored.
		/// </summary>
		public List<string> AllowedParams = new List<string>();

		#endregion

		#region Options

		protected NameValueCollection Options
		{
			get
			{
				if (_Options == null)
				{
					_Options = new NameValueCollection();
				}

				return _Options;
			}
		}
		private NameValueCollection _Options = null;

		protected string OptionsExpression
		{
			get
			{
				string optionsExpression = String.Empty;
				string optionDelimiter = ", ";
				string nameValueDelimiter = " : ";

				List<string> optionKeys = this.Options.AllKeys.ToList();
				foreach (string optionKey in optionKeys)
				{
					if (!String.IsNullOrEmpty(optionsExpression))
					{
						optionsExpression += optionDelimiter;
					}
					optionsExpression += optionKey + nameValueDelimiter + this.Options[optionKey].ToString().Trim().ToJSParam();
				}

				optionsExpression = "{ " + optionsExpression + " }";
				return optionsExpression;
			}
		}

		#endregion

		#region Callbacks

		[
		DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
		PersistenceMode(PersistenceMode.InnerProperty)
		]
		public InnerProperty CallbackScript
		{
			get
			{
				return _Callback;
			}
			set
			{
				_Callback = value;
				OnCallbackSet(_Callback);
			}
		}
		private InnerProperty _Callback;

		public virtual void OnCallbackSet()
		{
			OnCallbackSet(this.CallbackScript);
		}

		public virtual void OnCallbackSet(InnerProperty callback)
		{

		}

		protected bool IsCallbackEnabled
		{
			get;
			set;
		}

		public string CallbackExpression
		{
			get
			{
				string callbackExpression = String.Empty;

				if (this.IsCallbackEnabled && this.CallbackScript != null && !String.IsNullOrEmpty(this.CallbackScript.Text.TrimWhitespace()))
				{
					callbackExpression = Utility.Indent(this.Indent) + this.CallbackScript.Text.TrimWhitespace().ToFunctionExpression(true, this.Indent + 1);
					callbackExpression = Regex.Replace(callbackExpression, @"\;\s*;", ";");
				}

				return callbackExpression;
			}
		}


		[PersistenceMode(PersistenceMode.InnerProperty)]
		public List<JQuery> Callback
		{
			get;
			private set;
		}

		#endregion

		[PersistenceMode(PersistenceMode.InnerProperty)]
		public List<JQuery> Chain
		{
			get;
			private set;
		}

		public List<JQuery> ChildExpressions
		{
			get;
			private set;
		}

		public static JQuery Select()
		{
			JQuery result = new JQuery();

			return result;
		}

		public static JQuery Select(string selector)
		{
			JQuery result = new JQuery(selector);

			return result;
		}

		public static JQuery Select(Control control)
		{
			JQuery result = new JQuery(control);

			return result;
		}

		#region Hiding Properties & Events
		// Hiding properties & event so they don't show up in Intellisense. Some are overridden, like Visible.

		protected virtual event EventHandler Disposed;
		protected virtual event EventHandler DataBinding;
		protected virtual event EventHandler Load;
		protected virtual event EventHandler Init;
		protected virtual event EventHandler PreRender;
		protected virtual event EventHandler Unload;

		protected string AccessKey
		{
			get
			{
				return base.AccessKey;
			}
		}

		protected System.Drawing.Color BorderColor
		{
			get
			{
				return base.BorderColor;
			}
		}

		protected System.Drawing.Color BackColor
		{
			get
			{
				return base.BackColor;
			}
		}

		protected BorderStyle BorderStyle
		{
			get
			{
				return base.BorderStyle;
			}
		}

		protected Unit BorderWidth
		{
			get
			{
				return base.BorderWidth;
			}
		}

		protected string CssClass
		{
			get
			{
				return base.CssClass;
			}
		}

		protected bool EnableTheming
		{
			get
			{
				return base.EnableTheming;
			}
		}

		protected bool EnableViewState
		{
			get
			{
				return false;
			}
		}

		protected FontInfo Font
		{
			get
			{
				return null;
			}
		}

		protected System.Drawing.Color ForeColor
		{
			get
			{
				return base.ForeColor;
			}
		}

		protected Unit Height
		{
			get
			{
				return base.Height;
			}
		}

		protected string SkinID
		{
			get
			{
				return base.SkinID;
			}
		}

		protected short TabIndex
		{
			get
			{
				return base.TabIndex;
			}
		}

		protected string ToolTip
		{
			get
			{
				return base.ToolTip;
			}
		}

		public override bool Visible
		{
			get
			{
				return false;
			}
		}

		protected Unit Width
		{
			get
			{
				return base.Width;
			}
		}


		#endregion

	}

	public enum TriggerEvent
	{
		Click,
		Load,
		MouseOver,
		MouseOut,
		MouseDown,
		MouseUp,
		Focus,
		Blur,
		Change,
		Ready
	}
}
